using System;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;

namespace Atomic.Structures
{
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class Position
	{
		[NonSerialized] int hashCode;

		public Position(double x, double y, double z)
			: this(Basis.StandardBasis, x, y, z)
		{
		}

		public Position(Basis basis, double x, double y, double z)
			: this(basis, new SpaceVector(x, y, z))
		{
		}

		public Position(SpaceVector coordinates)
			: this(Basis.StandardBasis, coordinates)
		{
		}

		public Position(Basis basis, SpaceVector coordinates)
		{
			if (basis == null || coordinates == null)
			{
				throw new ArgumentNullException();
			}

			Basis = basis;
			Coordinates = coordinates;
	
			if (basis == Basis.StandardBasis)
			{
				// Don't perform arithmetics to avoid unnecessary round-off errors.
				PhysicalCoordinates = coordinates;
			}
			else
			{
				PhysicalCoordinates = basis.PhysicalMap * coordinates;
			}
		}

		public virtual bool Equals(Position other)
		{
			if (object.ReferenceEquals(other, null))
			{
				return false;
			}

			return EqualityHelper.Equals(this, other, new object[] { Basis, Coordinates }, new object[] { other.Basis, other.Coordinates }, hashCode, other.hashCode);
		}

		public override bool Equals(object other)
		{
			return Equals(other as Position);
		}
		
		public override int GetHashCode()
		{
			if (hashCode == 0)
			{
				hashCode = EqualityHelper.HashCode(new object[] { Basis, Coordinates });
			}

			return hashCode;
		}

		public static Position operator *(Position p, double a)
		{
			return new Position(p.Basis, p.Coordinates * a);
		}

		public static Position operator *(double a, Position p)
		{
			return p * a;
		}

		public static bool operator ==(Position p1, Position p2)
		{
			if (object.ReferenceEquals(p1, null))
			{
				if (object.ReferenceEquals(p2, null))
				{
					return true;
				}

				return false;
			}

			return p1.Equals(p2);
		}

		public static bool operator !=(Position p1, Position p2)
		{
			return !(p1 == p2);
		}

		public Basis Basis
		{
			get;
			private set;
		}

		public SpaceVector Coordinates // or: Coordinates, BasisCoordinates, RelativeCoordinates
		{
			get;
			private set;
		}

		public SpaceVector PhysicalCoordinates // or: RealCoordinates, AbsoluteCoordinates
		{
			get;
			private set;
		}

		private string DebuggerDisplay
		{
			get
			{
				return string.Format(CultureInfo.InvariantCulture, "({0:f4}, {1:f4}, {2:f4}) > ({3:f4}, {4:f4}, {5:f4})",
					Coordinates.X, Coordinates.Y, Coordinates.Z,
					PhysicalCoordinates.X, PhysicalCoordinates.Y, PhysicalCoordinates.Z);
			}
		}
	}
}
